package cn.da.shuai.cool.finance.service.impl;

import cn.da.shuai.cool.finance.model.CoolBudget;
import cn.da.shuai.cool.finance.mapper.CoolBudgetMapper;
import cn.da.shuai.cool.finance.service.ICoolBudgetService;
import cn.da.shuai.cool.mybatis.service.impl.CoolServiceImpl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class CoolBudgetServiceImpl extends CoolServiceImpl<CoolBudgetMapper, CoolBudget> implements ICoolBudgetService {

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean createBudget(CoolBudget budget) {
        budget.setStatus("DRAFT");
        budget.setActualAmount(BigDecimal.ZERO);
        budget.setVariance(budget.getAmount().negate());
        budget.setVariancePercentage(new BigDecimal("-100.00"));
        return save(budget);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateBudget(CoolBudget budget) {
        CoolBudget existingBudget = getById(budget.getId());
        if (existingBudget == null) {
            throw new RuntimeException("Budget not found");
        }

        if (!"DRAFT".equals(existingBudget.getStatus()) && !"REJECTED".equals(existingBudget.getStatus())) {
            throw new RuntimeException("Can only update draft or rejected budgets");
        }

        budget.setStatus("DRAFT");
        calculateVariance(budget);
        return updateById(budget);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean approveBudget(Long budgetId, Long approverId) {
        CoolBudget budget = getById(budgetId);
        if (budget == null) {
            throw new RuntimeException("Budget not found");
        }

        budget.setStatus("APPROVED");
        budget.setApprovedBy(approverId);
        budget.setApprovedDate(LocalDate.now());

        return updateById(budget);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean rejectBudget(Long budgetId, Long approverId, String reason) {
        CoolBudget budget = getById(budgetId);
        if (budget == null) {
            throw new RuntimeException("Budget not found");
        }

        budget.setStatus("REJECTED");
        budget.setNotes(reason);

        return updateById(budget);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean recordActual(Long budgetId, BigDecimal actualAmount) {
        CoolBudget budget = getById(budgetId);
        if (budget == null) {
            throw new RuntimeException("Budget not found");
        }

        budget.setActualAmount(actualAmount);
        calculateVariance(budget);

        return updateById(budget);
    }

    private void calculateVariance(CoolBudget budget) {
        BigDecimal variance = budget.getActualAmount().subtract(budget.getAmount());
        budget.setVariance(variance);

        if (budget.getAmount().compareTo(BigDecimal.ZERO) != 0) {
            BigDecimal variancePercentage = variance.multiply(new BigDecimal("100"))
                    .divide(budget.getAmount(), 2, RoundingMode.HALF_UP);
            budget.setVariancePercentage(variancePercentage);
        }
    }

    @Override
    public List<CoolBudget> getDepartmentBudgets(Long departmentId, Integer fiscalYear) {
        return list(new LambdaQueryWrapper<CoolBudget>()
                .eq(CoolBudget::getDepartmentId, departmentId)
                .eq(CoolBudget::getFiscalYear, fiscalYear)
                .orderBy(true, true, CoolBudget::getFiscalMonth));
    }

    @Override
    public Map<String, Object> getBudgetVarianceReport(Integer fiscalYear) {
        List<CoolBudget> budgets = list(new LambdaQueryWrapper<CoolBudget>()
                .eq(CoolBudget::getFiscalYear, fiscalYear));

        Map<String, Object> report = new HashMap<>();
        report.put("totalBudget", budgets.stream()
                .map(CoolBudget::getAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add));
        report.put("totalActual", budgets.stream()
                .map(CoolBudget::getActualAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add));
        report.put("totalVariance", budgets.stream()
                .map(CoolBudget::getVariance)
                .reduce(BigDecimal.ZERO, BigDecimal::add));

        Map<String, Map<String, BigDecimal>> departmentVariances = budgets.stream()
                .collect(Collectors.groupingBy(
                        CoolBudget::getDepartmentName,
                        Collectors.collectingAndThen(
                                Collectors.toList(),
                                list -> {
                                    Map<String, BigDecimal> variances = new HashMap<>();
                                    variances.put("budget", list.stream()
                                            .map(CoolBudget::getAmount)
                                            .reduce(BigDecimal.ZERO, BigDecimal::add));
                                    variances.put("actual", list.stream()
                                            .map(CoolBudget::getActualAmount)
                                            .reduce(BigDecimal.ZERO, BigDecimal::add));
                                    variances.put("variance", list.stream()
                                            .map(CoolBudget::getVariance)
                                            .reduce(BigDecimal.ZERO, BigDecimal::add));
                                    return variances;
                                }
                        )));

        report.put("departmentVariances", departmentVariances);
        return report;
    }

    @Override
    public Map<Integer, Map<String, BigDecimal>> getBudgetTrends(Integer fiscalYear) {
        List<CoolBudget> budgets = list(new LambdaQueryWrapper<CoolBudget>()
                .eq(CoolBudget::getFiscalYear, fiscalYear)
                .orderBy(true, true, CoolBudget::getFiscalMonth));

        Map<Integer, Map<String, BigDecimal>> monthlyTrends = budgets.stream()
                .collect(Collectors.groupingBy(
                        CoolBudget::getFiscalMonth,
                        Collectors.collectingAndThen(
                                Collectors.toList(),
                                list -> {
                                    Map<String, BigDecimal> amounts = new HashMap<>();
                                    amounts.put("budget", list.stream()
                                            .map(CoolBudget::getAmount)
                                            .reduce(BigDecimal.ZERO, BigDecimal::add));
                                    amounts.put("actual", list.stream()
                                            .map(CoolBudget::getActualAmount)
                                            .reduce(BigDecimal.ZERO, BigDecimal::add));
                                    return amounts;
                                }
                        )));

        return new HashMap<>(monthlyTrends);
    }

    @Override
    public Map<String, Object> getDepartmentComparison(Integer fiscalYear) {
        List<CoolBudget> budgets = list(new LambdaQueryWrapper<CoolBudget>()
                .eq(CoolBudget::getFiscalYear, fiscalYear));

        return budgets.stream()
                .collect(Collectors.groupingBy(
                        CoolBudget::getDepartmentName,
                        Collectors.collectingAndThen(
                                Collectors.toList(),
                                list -> {
                                    Map<String, Object> comparison = new HashMap<>();
                                    comparison.put("budget", list.stream()
                                            .map(CoolBudget::getAmount)
                                            .reduce(BigDecimal.ZERO, BigDecimal::add));
                                    comparison.put("actual", list.stream()
                                            .map(CoolBudget::getActualAmount)
                                            .reduce(BigDecimal.ZERO, BigDecimal::add));
                                    comparison.put("categories", list.stream()
                                            .collect(Collectors.groupingBy(
                                                    CoolBudget::getCategory,
                                                    Collectors.collectingAndThen(
                                                            Collectors.toList(),
                                                            categoryList -> {
                                                                Map<String, BigDecimal> categoryAmounts = new HashMap<>();
                                                                categoryAmounts.put("budget", categoryList.stream()
                                                                        .map(CoolBudget::getAmount)
                                                                        .reduce(BigDecimal.ZERO, BigDecimal::add));
                                                                categoryAmounts.put("actual", categoryList.stream()
                                                                        .map(CoolBudget::getActualAmount)
                                                                        .reduce(BigDecimal.ZERO, BigDecimal::add));
                                                                return categoryAmounts;
                                                            }
                                                    ))));
                                    return comparison;
                                }
                        )));
    }

    @Override
    public List<Map<String, Object>> getForecastVsActual(Integer fiscalYear) {
        List<CoolBudget> budgets = list(new LambdaQueryWrapper<CoolBudget>()
                .eq(CoolBudget::getFiscalYear, fiscalYear)
                .orderBy(true, true, CoolBudget::getFiscalMonth));

        return budgets.stream()
                .collect(Collectors.groupingBy(
                        CoolBudget::getFiscalMonth,
                        Collectors.collectingAndThen(
                                Collectors.toList(),
                                list -> {
                                    Map<String, Object> comparison = new HashMap<>();
                                    comparison.put("month", list.getFirst().getFiscalMonth());
                                    comparison.put("forecast", list.stream()
                                            .map(CoolBudget::getAmount)
                                            .reduce(BigDecimal.ZERO, BigDecimal::add));
                                    comparison.put("actual", list.stream()
                                            .map(CoolBudget::getActualAmount)
                                            .reduce(BigDecimal.ZERO, BigDecimal::add));
                                    return comparison;
                                }
                        )))
                .values()
                .stream()
                .sorted(Comparator.comparing(m -> (Integer) m.get("month")))
                .collect(Collectors.toList());
    }
}